package com.media.service.impl;

import com.media.cache.CacheUtil;
import com.media.common.Constants;
import com.media.common.enums.PushType;
import com.media.common.exception.ServiceException;
import com.media.common.ffmpeg.FFmpegError;
import com.media.common.ffmpeg.FFmpegUtil;
import com.media.config.MediaCaptorLiveConfig;
import com.media.pojo.CameraPojo;
import com.media.service.CameraService;
import com.media.thread.CameraThread;
import com.media.utils.DateUtils;
import com.media.utils.IpUtil;
import com.media.utils.StringUtils;
import com.media.utils.ToolUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;

/**
 * 设备相关
 */
@Service
@Slf4j
public class CameraServiceImpl implements CameraService {

    @Resource
    public MediaCaptorLiveConfig mediaCaptorLiveConfig;

    @Override
    public CameraPojo openCameraRtsp(CameraPojo cameraPojo) {
//      1 : 校验参数
        inspect(cameraPojo);
//      2 : 拼接 rtsp
        String rtsp = FFmpegUtil.getRtsp(
                cameraPojo.getUsername(),
                cameraPojo.getPassword(),
                cameraPojo.getIp(),
                cameraPojo.getChannel(),
                cameraPojo.getStream());
        cameraPojo.setRtsp(rtsp);
        return cameraPojo;
    }


    /**
     * 打开摄像头
     */
    @Override
    public CameraPojo openCamera(CameraPojo cameraPojo) {
//      1 : 校验参数
        inspect(cameraPojo);
//      2 : 获取当前时间 (流打开时间)
        String openTime = DateUtils.getTime();
        cameraPojo.setOpenTime(openTime);
//      3 : 获取已经打开的流 , 如果当前流已经推送了 , 则不进行推送了
//          注意 : 我这里没有判断 流是rtmp, 还是hls , 因为实际业务中 ,不可能使用两种直播方式
        Set<String> keys = CacheUtil.STREAM_MAP.keySet();
//      4 : 判断当前摄像头 , 是否已经开启直播流了
//          4.1 : 是否存在的标志；0：不存在；1：存在
        int sign = 0;
//          4.2 : 循环遍历判断是否存在已打开的流
        for (String key : keys) {
            if (cameraPojo.getIp().equals(CacheUtil.STREAM_MAP.get(key).getIp())
                    && cameraPojo.getChannel().equals(CacheUtil.STREAM_MAP.get(key).getChannel())
            ) {// 存在直播流
                cameraPojo = CacheUtil.STREAM_MAP.get(key);
                sign = 1; //标记存在的标志
                break; //结束循环
            }
        }
//      5 : 判断流是否已存在
//            5.1 : 存在
        if (sign == 1) {
//              5.1.1存在 , 设置流当前在线数 + 1
            cameraPojo.setCount(cameraPojo.getCount() + 1);
//              5.1.2 : 重置流的失效时间,当前时间
            cameraPojo.setOpenTime(openTime);
        } else {
//              5.2 不存在 (则开始推流)
            openStream(cameraPojo);
        }
        return cameraPojo;
    }

    /**
     * 录制视频
     */
    @Override
    public String recordVideo(CameraPojo cameraPojo) {
//        创建文件夹
        ToolUtils.mkdirs(cameraPojo.getPath());
        String rtsp = openCameraRtsp(cameraPojo).getRtsp();
        String realFile = cameraPojo.getPath() + System.currentTimeMillis() + Constants.MP4;
//        rtsp 转换成视频
        InputStream inputStream;
        BufferedReader input;
        try {
            List<String> commend = FFmpegUtil.rtspToMp4(rtsp, realFile, cameraPojo.getTime());
            ProcessBuilder builder = new ProcessBuilder(commend);
            builder.redirectErrorStream(true);
            inputStream = builder.start().getInputStream();

            input = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = input.readLine()) != null) {
                log.debug("ffmpeg input={}", line);
                for (String e : FFmpegError.getErrors()) {
                    if (Optional.of(line).filter(r -> r.contains(e)).isPresent()) {
                        log.debug("ffmpeg mp4 error={}", line);
                    }
                }
            }
            log.info("视频录制成功 : {} : {}", rtsp, realFile);
            return realFile;
        } catch (Exception e) {
            log.error("录制视频 , 出现异常 ; {}", rtsp, e);
        }
        return null;
    }

    /**
     * 裁剪视频
     */
    @Override
    public String cutOutVideo(String sourceUrl, String toUrl, String startTime, String endTime) {
//        裁剪视频
        InputStream inputStream;
        BufferedReader input;
        try {
            List<String> commend = FFmpegUtil.cutOutVideo(sourceUrl, toUrl, startTime, startTime);
            ProcessBuilder builder = new ProcessBuilder(commend);
            builder.redirectErrorStream(true);
            inputStream = builder.start().getInputStream();

            input = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = input.readLine()) != null) {
                log.debug("ffmpeg input={}", line);
                for (String e : FFmpegError.getErrors()) {
                    if (Optional.of(line).filter(r -> r.contains(e)).isPresent()) {
                        log.debug("ffmpeg mp4 error={}", line);
                    }
                }
            }
            log.info("视频裁剪成功 : {} --> {}", sourceUrl, toUrl);
            return toUrl;
        } catch (Exception e) {
            log.error("裁剪视频 , 出现异常 ; {} --> {}", sourceUrl, toUrl, e);
        }
        return null;
    }

    /**
     * 录制图片
     */
    @Override
    public String recordImage(CameraPojo cameraPojo) {
//        创建文件夹
        ToolUtils.mkdirs(cameraPojo.getPath());
        String rtsp = openCameraRtsp(cameraPojo).getRtsp();
        String realFile = cameraPojo.getPath() + System.currentTimeMillis() + Constants.JPG;
//        rtsp 转换成视频
        InputStream inputStream;
        BufferedReader input;
        try {
            List<String> commend = FFmpegUtil.rtspToImage(rtsp, realFile);
            ProcessBuilder builder = new ProcessBuilder(commend);
            builder.redirectErrorStream(true);
            inputStream = builder.start().getInputStream();

            input = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = input.readLine()) != null) {
                log.debug("ffmpeg image input={}", line);
                for (String e : FFmpegError.getErrors()) {
                    if (Optional.of(line).filter(r -> r.contains(e)).isPresent()) {
                        log.debug("ffmpeg image error={}", line);
                    }
                }
            }
            log.info("图片录制成功 : {} : {}", rtsp, realFile);
            return realFile;
        } catch (Exception e) {
            log.error("录制图片 , 出现异常 ; {}", rtsp, e);
        }
        return null;
    }

    /**
     * 录制图片 (一秒一帧)
     */
    @Override
    public String recordContinueToImage(CameraPojo cameraPojo) {
        //        创建文件夹
        ToolUtils.mkdirs(cameraPojo.getPath());
        String rtsp = openCameraRtsp(cameraPojo).getRtsp();
//        rtsp 转换成视频
        InputStream inputStream;
        BufferedReader input;
        try {
            List<String> commend = FFmpegUtil.rtspContinueToImage(rtsp, cameraPojo.getPath(), cameraPojo.getTime());
            ProcessBuilder builder = new ProcessBuilder(commend);
            builder.redirectErrorStream(true);
            inputStream = builder.start().getInputStream();

            input = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = input.readLine()) != null) {
                log.debug("ffmpeg continue image input={}", line);
                for (String e : FFmpegError.getErrors()) {
                    if (Optional.of(line).filter(r -> r.contains(e)).isPresent()) {
                        log.debug("ffmpeg continue image error={}", line);
                    }
                }
            }
            log.info("图片录制成功 : {} : {}", rtsp, cameraPojo.getPath());
            return cameraPojo.getPath();
        } catch (Exception e) {
            log.error("录制图片 , 出现异常 ; {}", rtsp, e);
        }
        return null;
    }

    @Override
    public void closeCamera(String tokens) {
        if (StringUtils.isNotEmpty(tokens)) {
            String[] tokenArr = tokens.split(",");
            for (String token : tokenArr) {
//                循环token , 把流的在线人数 -1 , 方便一会线程自动释放
                if (CacheUtil.JOB_MAP.containsKey(token) && CacheUtil.STREAM_MAP.containsKey(token)) {
                    if (0 < CacheUtil.STREAM_MAP.get(token).getCount()) {
                        // 人数-1
                        CacheUtil.STREAM_MAP.get(token).setCount(CacheUtil.STREAM_MAP.get(token).getCount() - 1);
                    }
                }
            }
        }
    }

    @Override
    public void keepAlive(String tokens) {
        // 校验参数
        if (StringUtils.isNotEmpty(tokens)) {
            String[] tokenArr = tokens.split(",");
            for (String token : tokenArr) {
                CameraPojo cameraPojo;
                // 直播流token
                if (null != CacheUtil.STREAM_MAP.get(token)) {
                    cameraPojo = CacheUtil.STREAM_MAP.get(token);
                    // 更新流打开时间 (这样就会给当前流增加五分钟的失效期)
                    cameraPojo.setOpenTime(DateUtils.getTime());
                }
            }
        }
    }

    @Override
    public Map<String, Object> getConfig() {
        // 获取当前时间
        long nowTime = new Date().getTime();
        String upTime = (nowTime - CacheUtil.START_TIME) / (1000 * 60 * 60) + "h"
                + (nowTime - CacheUtil.START_TIME) % (1000 * 60 * 60) / (1000 * 60) + "m"
                + (nowTime - CacheUtil.START_TIME) % (1000 * 60 * 60) / (1000) + "s";
        Map<String, Object> status = new HashMap<>();
        status.put("config", mediaCaptorLiveConfig);
        status.put("uptime", upTime);
        return status;
    }

    /**
     * 推流
     * ip       摄像头ip
     * username 摄像头名字
     * password 摄像头密码
     * channel  通道
     * stream   流
     * openTime 打开时间
     * 推流器
     **/
    private void openStream(CameraPojo cameraPojo) {
//      1 : 生成token (每一个流都生成一个唯一token (后面保活流 , 都用到此token))
        String token = UUID.randomUUID().toString();
        String IP = IpUtil.IpConvert(cameraPojo.getIp());
//      2 : 生成RTSP
        CameraPojo rtspCameraPojo = openCameraRtsp(cameraPojo);
//      3 : 生成RTMP的地址
        String rtmp = Constants.RTMP + IpUtil.IpConvert(mediaCaptorLiveConfig.getPushIp())
                + ":" + mediaCaptorLiveConfig.getRtmpPushPort() + "/live/" + token;

//     4 : 判断当前流 , 是否是HLS
        if (PushType.HLS.equals(cameraPojo.getPushType())) {
//          4.1 : 创建文件夹
            ToolUtils.mkdirs(mediaCaptorLiveConfig.getHlsPushPath());
//          4.2 : 生成访问连接
            String hls = Constants.HTTP + mediaCaptorLiveConfig.getPushIp()
                    + ":" + mediaCaptorLiveConfig.getHlsPushPort()
                    + "/hls/" + token + Constants.M3U8;
            cameraPojo.setHls(hls);
        }
        cameraPojo.setIp(IP);
        cameraPojo.setRtsp(rtspCameraPojo.getRtsp());
        cameraPojo.setRtmp(rtmp);
        cameraPojo.setCount(1);
        cameraPojo.setToken(token);
//      5 :放入线程池 执行任务
        CameraThread.MyRunnable job = new CameraThread.MyRunnable(cameraPojo, mediaCaptorLiveConfig);
        CameraThread.MyRunnable.es.execute(job);
//      6 : 记录一下当前已启动的线程
        CacheUtil.JOB_MAP.put(token, job);
    }


    /**
     * 检查数据
     *
     * @param cameraPojo 要检查的数据
     */
    public void inspect(CameraPojo cameraPojo) {
        if (StringUtils.isEmpty(cameraPojo.getIp()) &&
                StringUtils.isEmpty(cameraPojo.getUsername()) &&
                StringUtils.isEmpty(cameraPojo.getPassword()) &&
                StringUtils.isEmpty(cameraPojo.getStream()) &&
                StringUtils.isEmpty(cameraPojo.getChannel())) {
            throw new ServiceException("关键数据为空,请检测数据完整性!");
        }
    }
}
